/* DefaultEntityMapper.java
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

package dowry.impl;

import java.beans.*;
import java.lang.reflect.*;
import java.util.*;
import dowry.*;
import dowry.datatype.*;

/**
 * DefaultEntityMapper provides a basic implementation of
 * EntityMapper.  In this default implementation, all these
 * methods simply return empty collections.
 *
 */
public class DefaultEntityMapper implements EntityMapper
{
    private DatatypeManager datatypeMgr = null;
    private Set             classes     = new HashSet();
    private Map             entities    = new HashMap();
    private Set             types       = new HashSet();

    /**
     * Augments the entity mapping based on the specified
     * attribute value.  This will override any existing
     * value for this attribute.
     *
     * @param entity     the string name of the entity
     *
     * @param property   the string name of the property
     *
     * @param attribute  the string name of the attribute
     *
     * @param value      the attribute value to be included
     *
     */
    public void addAttribute(String entity,
                             String property,
                             String attribute,
                             Object value)
    {
        if (entity != null &&
            property != null &&
            attribute != null)
        {
            Map propertyMap = (Map) entities.get(entity);
            if (propertyMap == null)
            {
                propertyMap = new HashMap();
                entities.put(entity, propertyMap);
            }

            Map attributeMap = (Map) propertyMap.get(property);
            if (attributeMap == null)
            {
                attributeMap = new HashMap();
                propertyMap.put(property, attributeMap);
            }

            attributeMap.put(attribute, value);

            if (attribute.equals(AbstractType.JAVA_CLASS) && value != null)
            {
                Class c = getClass(value);
                classes.add(c);
            }
            else if (attribute.equals(AbstractType.TYPE) && value != null)
            {
                types.add(value);
            }
        }
    }

    /**
     * Augments the entity mapping based on the structure of
     * the provided JavaBean's class.  This will generate
     * entity mapping info for the properties of the object
     * and their types.
     *
     * @param c  the class of a JavaBean object to be
     *           used for generating attributes
     *
     */
    public void addBean(Class c)
    {
        DatatypeManager datatypeMgr = getDatatypeManager();
        if (c != null && datatypeMgr != null)
        {
            Datatype datatype = datatypeMgr.getDatatype(c);
            if (datatype != null)
            {
                String type = datatype.toType(c);

                classes.add(c);
                types.add(type);

                String entityName = getEntityName(c, type);

                try
                {
                    BeanInfo info = Introspector.getBeanInfo(c);
                    PropertyDescriptor[] propertyDescriptors =
                        info.getPropertyDescriptors();

                    String propertyName;
                    for (int i=0; i < propertyDescriptors.length; i++)
                    {
                        propertyName = propertyDescriptors[i].getName();
                        c = propertyDescriptors[i].getPropertyType();

                        datatype = datatypeMgr.getDatatype(c);
                        if (datatype != null && c != null)
                        {
                            type = datatype.toType(c);

                            addAttribute(entityName,
                                         propertyName,
                                         AbstractType.JAVA_CLASS,
                                         c.getName());

                            addAttribute(entityName,
                                         propertyName,
                                         AbstractType.TYPE,
                                         type);
                        }
                    }
                }
                catch(IntrospectionException iex)
                {
                }
            }
        }
    }

    /**
     * Returns the classes for the mapped entities.
     *
     * @return  a set of class objects representing the
     *          mapped entities
     *
     */
    public Set getClasses()
    {
        return classes;
    }

    /**
     * Returns the datatype manager to be used by the
     * entity mapper.
     *
     * @return  the datatype manager instance
     *
     */
    public DatatypeManager getDatatypeManager()
    {
        return datatypeMgr;
    }

    /**
     * Returns a mapping of Dowry entities.  The format
     * of this map is:
     *
     * <pre>
     * Map&lt;String entity,
     *     Map&lt;String property,
     *         Map&lt;String attribute, Object value&gt;&gt;&gt;
     * </pre>
     *
     * <p>
     * In this default implementation, this method does
     * nothing.
     * </p>
     *
     * @return  a map representing the Dowry entities
     *
     */
    public Map getEntityMapping()
    {
        return entities;
    }

    /**
     * Returns the string names for the mapped entities.
     *
     * <p>
     * In this default implementation, this method does
     * nothing.
     * </p>
     *
     * @return  a set of the the string names of the mapped
     *          entities
     *
     */
    public Set getNames()
    {
        return getEntityMapping().keySet();
    }

    /**
     * Returns a set of all the string Dowry types
     * referenced in this mapping.  This includes both the
     * types of all mapped properties, as well as the types
     * of the entities themselves.
     *
     * <p>
     * In this default implementation, this method does
     * nothing.
     * </p>
     *
     * @return a set of string names of the Dowry types
     *         represented in the mapping
     *
     */
    public Set getTypes()
    {
        return types;
    }

    /**
     * Sets the datatype manager to be used by the
     * entity mapper.
     *
     * @param datatypeMgr   the datatype manager instance
     *
     */
    public void setDatatypeManager(DatatypeManager datatypeMgr)
    {
        this.datatypeMgr = datatypeMgr;
    }

    /**
     * Returns a class object.  It can be either a string or
     * a class.
     *
     * @param value  the object value
     *
     * @return       the class
     *
     */
    protected Class getClass(Object value)
    {
        Class c = null;

        if (value instanceof Class)
        {
            c = (Class) value;
        }
        else
        {
            try
            {
                c = Class.forName(value.toString());
            }
            catch(ClassNotFoundException ex)
            {
            }
        }

        return c;
    }

    /**
     * Gets the Dowry name for this entity, based on
     * the entity's class and datatype.  This name
     * should be the Java class name of the peristent
     * class, with the package stripped off.
     *
     * @param c       the class
     *
     * @param type    the Dowry type
     *
     * @return        the Dowry name for this entity
     *
     */
    protected String getEntityName(Class c, String type)
    {
        String entityName = null;

        if (type != null)
        {
            int delim = type.indexOf(AbstractType.ENTITY_PREFIX);
            if (delim == 0)
            {
                entityName = type.substring(
                    AbstractType.ENTITY_PREFIX.length() + 1);
            }
            else
            {
                entityName = type;
            }
        }

        return entityName;
    }
}